package com.audition.thread.threadsecurity;

/**
 * @author xukang
 * @date 2021-03-08 16:10
 */
public class Introduce {
    /**
     * 线程安全问题
     * 运行结果错误；
     * 发布和初始化导致线程安全问题；
     * 活跃性问题。:最典型的有三种，分别为死锁、活锁和饥饿。
     * 活锁:第二种活跃性问题是活锁，活锁与死锁非常相似，也是程序一直等不到结果，
     * 但对比于死锁，活锁是活的，什么意思呢？因为正在运行的线程并没有阻塞，它始终在运行中，却一直得不到结果。
     * 举一个例子，假设有一个消息队列，队列里放着各种各样需要被处理的消息，
     * 而某个消息由于自身被写错了导致不能被正确处理，执行时会报错，
     * 可是队列的重试机制会重新把它放在队列头进行优先重试处理，
     * 但这个消息本身无论被执行多少次，都无法被正确处理，
     * 每次报错后又会被放到队列头进行重试，周而复始，最终导致线程一直处于忙碌状态，
     * 但程序始终得不到结果，便发生了活锁问题。
     * 饥饿:第三个典型的活跃性问题是饥饿，饥饿是指线程需要某些资源时始终得不到，
     * 尤其是CPU 资源，就会导致线程一直不能运行而产生的问题。在 Java 中有线程优先级的概念，
     * Java 中优先级分为 1 到 10，1 最低，10 最高。
     * 如果我们把某个线程的优先级设置为 1，这是最低的优先级，
     * 在这种情况下，这个线程就有可能始终分配不到 CPU 资源，而导致长时间无法运行。
     * 或者是某个线程始终持有某个文件的锁，而其他线程想要修改文件就必须先获取锁，
     * 这样想要修改文件的线程就会陷入饥饿，长时间不能运行。
     * 好了，今天的内容就全部讲完了，
     * 通过本课时的学习我们知道了线程安全问题主要有 3 种，i++ 等情况导致的运行结果错误，
     * 通常是因为并发读写导致的，第二种是对象没有在正确的时间、地点被发布或初始化，
     * 而第三种线程安全问题就是活跃性问题，包括死锁、活锁和饥饿。
     */
    /**：哪些场景需要额外注意线程安全问题？
     * 共享的变量或资源带来的线程安全问题 : i++的问题
     * 依赖时序的操作:这样类似的场景都是同样的道理，“检查与执行”并非原子性操作，
     * 在中间可能被打断，而检查之后的结果也可能在执行时已经过期、无效，
     * 换句话说，获得正确结果取决于幸运的时序。这种情况下，
     * 我们就需要对它进行加锁等保护措施来保障操作的原子性。
     * 不同数据之间存在绑定关系:第三种需要我们注意的线程安全场景是不同数据之间存在相互绑定关系的情况。有时候，我们的不同数据之间是成组出现的，存在着相互对应或绑定的关系，最典型的就是 IP 和端口号。
     * 有时候我们更换了 IP，往往需要同时更换端口号，如果没有把这两个操作绑定在一起，
     * 就有可能出现单独更换了 IP 或端口号的情况，而此时信息如果已经对外发布，
     * 信息获取方就有可能获取一个错误的 IP 与端口绑定情况，这时就发生了线程安全问题。
     * 在这种情况下，我们也同样需要保障操作的原子性。
     * 对方没有声明自己是线程安全的:第四种值得注意的场景是在我们使用其他类时，如果对方没有声明自己是线程安全的，那么这种情况下对其他类进行多线程的并发操作，就有可能会发生线程安全问题。
     * 举个例子，比如说我们定义了 ArrayList，它本身并不是线程安全的，
     * 如果此时多个线程同时对 ArrayList 进行并发读/写，那么就有可能会产生线程安全问题，
     * 造成数据出错，而这个责任并不在 ArrayList，因为它本身并不是并发安全的
     */
}
/*多线程的性能问题:
调度开销:
    上下文切换
    首先，我们看一下线程调度，在实际开发中，线程数往往是大于 CPU 核心数的，
    比如 CPU 核心数可能是 8 核、16 核，等等，但线程数可能达到成百上千个。这种情况下，
    操作系统就会按照一定的调度算法，给每个线程分配时间片，让每个线程都有机会得到运行。
    而在进行调度时就会引起上下文切换，上下文切换会挂起当前正在执行的线程并保存当前的状态，
    然后寻找下一处即将恢复执行的代码，唤醒下一个线程，以此类推，反复执行。
    但上下文切换带来的开销是比较大的，假设我们的任务内容非常短，比如只进行简单的计算，
    那么就有可能发生我们上下文切换带来的性能开销比执行线程本身内容带来的开销还要大的情况。
    缓存失效
    不仅上下文切换会带来性能问题，缓存失效也有可能带来性能问题。由于程序有很大概率会再次访问刚才访问过的数据，所以为了加速整个程序的运行，会使用缓存，这样我们在使用相同数据时就可以很快地获取数据。可一旦进行了线程调度，切换到其他线程，CPU就会去执行不同的代码，
    原有的缓存就很可能失效了，需要重新缓存新的数据，这也会造成一定的开销，
    所以线程调度器为了避免频繁地发生上下文切换，通常会给被调度到的线程设置最小的执行时间，
    也就是只有执行完这段时间之后，才可能进行下一次的调度，由此减少上下文切换的次数。
协作开销
    除了线程调度之外，线程协作同样也有可能带来性能问题。因为线程之间如果有共享数据，为了避免数据错乱，
    为了保证线程安全，就有可能禁止编译器和 CPU 对其进行重排序等优化，也可能出于同步的目的，
    反复把线程工作内存的数据 flush 到主存中，然后再从主内存 refresh 到其他线程的工作内存中，等等。
    这些问题在单线程中并不存在，但在多线程中为了确保数据的正确性，就不得不采取上述方法，
    因为线程安全的优先级要比性能优先级更高，这也间接降低了我们的性能。
 */